import { promises as fs, Dirent } from 'fs'
import { join, basename, dirname } from 'path'
import * as XLSX from 'xlsx'
import { ConverterOptions, JsonRecord } from './types'

export class JsonToExcelConverter {
  private readonly opts: Required<ConverterOptions>

  constructor(options: ConverterOptions = {}) {
    this.opts = {
      inputDir: options.inputDir ?? './json',
      outputFile: options.outputFile ?? './output.xlsx',
      sheetName: options.sheetName ?? 'Data',
      flatten: options.flatten ?? true,
      includeSubdirs: options.includeSubdirs ?? false,
      filePattern: options.filePattern ?? /\.json$/i,
      exportMode: options.exportMode ?? 'horizontal',
    } as Required<ConverterOptions>
  }

  /* 主入口 */
  async convert(): Promise<string> {
    const data =
      this.opts.exportMode === 'vertical'
        ? await this.buildVertical()
        : await this.readJsonFiles()
    if (!data.length) throw new Error('No data to convert')
    return this.writeExcel(data)
  }

  /* =============== 横向模式（原逻辑） =============== */
  private async readJsonFiles(): Promise<JsonRecord[]> {
    const files = await this.locateJson(this.opts.inputDir)
    const out: JsonRecord[] = []
    for (const f of files) {
      try {
        const raw = JSON.parse(await fs.readFile(f, 'utf8'))
        const arr = Array.isArray(raw) ? raw : [raw]
        const flattened = this.opts.flatten
          ? arr.map((o) => this.flatten(o))
          : arr
        flattened.forEach((row) =>
          out.push({ ...row, __source_file: basename(f) })
        )
      } catch (e) {
        console.warn(`Skip ${f}:`, (e as Error).message)
      }
    }
    return out
  }

  /* =============== 竖向模式（i18n） =============== */
  private async buildVertical(): Promise<JsonRecord[]> {
    const files = await this.locateJson(this.opts.inputDir)
    const langMap: Record<string, Record<string, string>> = {} // lang -> flatObj
    const allKeys = new Set<string>()

    for (const f of files) {
      const lang = basename(f).replace(this.opts.filePattern, '') // en / zh
      const flat = this.flatten(await this.readJsonFile(f))
      langMap[lang] = flat
      Object.keys(flat).forEach((k) => allKeys.add(k))
    }

    return Array.from(allKeys).map((k) => {
      const row: JsonRecord = { key: k }
      Object.entries(langMap).forEach(([lang, map]) => {
        row[lang] = map[k] ?? ''
      })
      return row
    })
  }

  /* 通用工具：递归找文件 */
  private async locateJson(root: string): Promise<string[]> {
    const files: string[] = []
    const walk = async (dir: string) => {
      for (const d of await fs.readdir(dir, { withFileTypes: true })) {
        const full = join(dir, d.name)
        if (d.isDirectory() && this.opts.includeSubdirs) await walk(full)
        if (d.isFile() && this.opts.filePattern.test(d.name)) files.push(full)
      }
    }
    await walk(root)
    return files
  }

  private async readJsonFile(f: string): Promise<any> {
    return JSON.parse(await fs.readFile(f, 'utf8'))
  }

  /* 扁平化 */
  private flatten(obj: unknown, prefix = '', res: JsonRecord = {}): JsonRecord {
    if (obj === null || obj === undefined) return res
    if (typeof obj !== 'object' || obj instanceof Date) {
      res[prefix] = obj
      return res
    }
    if (Array.isArray(obj)) {
      obj.forEach((v, i) =>
        this.flatten(v, prefix ? `${prefix}[${i}]` : `[${i}]`, res)
      )
      return res
    }
    for (const [k, v] of Object.entries(obj)) {
      const key = prefix ? `${prefix}.${k}` : k
      this.flatten(v, key, res)
    }
    return res
  }

  /* 写 Excel */
  private async writeExcel(data: JsonRecord[]): Promise<string> {
    const wb = XLSX.utils.book_new()
    const ws = XLSX.utils.json_to_sheet(data)
    XLSX.utils.book_append_sheet(wb, ws, this.opts.sheetName)
    await fs.mkdir(dirname(this.opts.outputFile), { recursive: true })
    XLSX.writeFile(wb, this.opts.outputFile)
    return this.opts.outputFile
  }
}
